package com.silence.gmall.util;

import com.google.common.collect.Maps;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;

/**
 * @Author silence
 * @Email 792245022@qq.com
 * @Date 2025-06-11 14:50
 * @Description aop 和 el 工具类
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class AopAndElUtil {

    /**
     * 表达式解析器
     */
    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();

    /**
     * 表达式 MAP
     */
    private static final ConcurrentMap<String, Expression> EXPRESSION_MAP = Maps.newConcurrentMap();

    /**
     * 构建表达式上下文
     *
     * @param point  切点
     * @param result 结果
     * @return 表达式内容
     */
    public static EvaluationContext buildContext(JoinPoint point, Object result) {
        EvaluationContext context = buildContext(point);
        context.setVariable("result", result);
        return context;
    }

    /**
     * 构建表达式上下文
     *
     * @param point 切点
     * @return 表达式内容
     */
    public static EvaluationContext buildContext(JoinPoint point) {
        EvaluationContext context = new StandardEvaluationContext();
        Object[] args = point.getArgs();

        if (ArrayUtils.isEmpty(args)) {
            return context;
        }

        MethodSignature ms = (MethodSignature) point.getSignature();
        String[] parameterNames = ms.getParameterNames();

        if (ArrayUtils.isEmpty(parameterNames)) {
            return context;
        }

        for (int i = 0; i < parameterNames.length; ++i) {
            context.setVariable(parameterNames[i], args[i]);
        }

        return context;
    }

    /**
     * 解析表达式
     *
     * @param key 表达式 key
     * @return 表达式对象
     */
    public static Expression getExpressionByKey(String key) {
        if (EXPRESSION_MAP.containsKey(key)) {
            return EXPRESSION_MAP.get(key);
        }
        Expression expression = EXPRESSION_PARSER.parseExpression(key, ParserContext.TEMPLATE_EXPRESSION);
        EXPRESSION_MAP.put(key, expression);
        return expression;
    }

    /**
     * 获取表达式值
     *
     * @param key     表达式 key
     * @param context 表达式上下文
     * @param clazz  表达式类型
     * @return 表达式值
     */
    public static <T> T getExpressionValueByKey(String key, EvaluationContext context, Class<T> clazz) {
        Expression expressionByKey = getExpressionByKey(key);
        return expressionByKey.getValue(context, clazz);
    }

    /**
     * 获取连接点上的注解
     *
     * @param point          连接点
     * @param annotationType 注解类型
     * @param <T>            注解类
     * @return T 注解
     */
    public static <T extends Annotation> T getMethodAnnotation(ProceedingJoinPoint point, Class<T> annotationType) {
        // 获取类的字节码对象，通过字节码对象获取方法信息
        Class<?> targetCls = point.getTarget().getClass();
        // 获取方法签名(通过此签名获取目标方法信息)
        MethodSignature ms = (MethodSignature) point.getSignature();
        // 获取目标方法上的注解指定的操作名称
        Method targetMethod;
        try {
            targetMethod = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
        } catch (NoSuchMethodException _) {
            return null;
        }
        return targetMethod.getAnnotation(annotationType);
    }

    /**
     * 获取连接点上的注解
     *
     * @param point 连接点
     * @return 注解数组
     */
    public static Annotation[] getMethodAnnotations(ProceedingJoinPoint point) {
        Class<?> targetCls = point.getTarget().getClass();
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method targetMethod;

        try {
            targetMethod = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
        } catch (NoSuchMethodException e) {
            return new Annotation[0];
        }

        return targetMethod.getAnnotations();
    }

    /**
     * 获取方法返回值类型
     *
     * @param point 连接点
     * @return 返回值类型
     */
    public static Class<?> getMethodReturnType(ProceedingJoinPoint point) {
        Class<?> targetCls = point.getTarget().getClass();
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method targetMethod;

        try {
            targetMethod = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
        } catch (NoSuchMethodException _) {
            return null;
        }

        return targetMethod.getReturnType();
    }

    /**
     * 获取方法真实的返回值类型
     *
     * @param point 连接点
     * @return 返回值类型
     */
    public static Type getMethodGenericReturnType(ProceedingJoinPoint point) {
        Class<?> targetCls = point.getTarget().getClass();
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method targetMethod;

        try {
            targetMethod = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
        } catch (NoSuchMethodException _) {
            return null;
        }

        return targetMethod.getGenericReturnType();
    }

    /**
     * 获取类上的注解
     *
     * @param point          连接点
     * @param annotationType 注解类型
     * @param <T>            泛型
     * @return 注解
     */
    public static <T extends Annotation> T getClassAnnotation(ProceedingJoinPoint point, Class<T> annotationType) {
        Class<?> targetCls = point.getTarget().getClass();
        return targetCls.getAnnotation(annotationType);
    }

    /**
     * 获取方法参数
     *
     * @param point 连接点啊
     * @return 方法参数
     */
    public static Map<String, Object> getParameters(ProceedingJoinPoint point) {
        MethodSignature ms = (MethodSignature) point.getSignature();
        String[] parameterNames = ms.getParameterNames();
        Object[] args = point.getArgs();
        Map<String, Object> paramMap = Maps.newHashMap();

        for (int i = 0; i < parameterNames.length; ++i) {
            paramMap.put(parameterNames[i], args[i]);
        }

        return paramMap;
    }
}
